package cn.virens.database.common.mapper;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;

import cn.hutool.core.util.StrUtil;
import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.util.StringUtil;

public class ExampleWrapper extends Example {
	private final List<ExampleJoin> joinTables = new ArrayList<>();
	private final List<ExampleHaving> havings = new ArrayList<>();

	private String groupByClause;

	public ExampleWrapper(Class<?> entityClass) {
		super(entityClass);
	}

	public static <T> ExampleWrapper of(Class<T> clazz) {
		return new ExampleWrapper(clazz);
	}

	public static <T> ExampleWrapper of(Class<T> clazz, Map<String, Object> param) {
		final ExampleWrapper answer = new ExampleWrapper(clazz);
		answer.createCriteria(param);

		return answer;
	}

	@Override
	public Example selectProperties(String... properties) {
		if (properties == null || properties.length <= 0) { return this; }

		if (this.selectColumns == null) {
			this.selectColumns = new LinkedHashSet<>();
		}

		EntityColumn column = null;

		for (String property : properties) {
			if ((column = propertyMap.get(property)) != null) {
				this.selectColumns.add(column.getColumn());
			} else {
				this.selectColumns.add(property);
			}
		}

		return this;
	}

	@Override
	public Example excludeProperties(String... properties) {
		if (properties == null || properties.length <= 0) { return this; }

		if (this.excludeColumns == null) {
			this.excludeColumns = new LinkedHashSet<String>();
		}

		EntityColumn column = null;

		for (String property : properties) {
			if ((column = propertyMap.get(property)) != null) {
				this.excludeColumns.add(column.getColumn());
			} else {
				this.excludeColumns.add(property);
			}
		}

		return this;
	}

	public ExampleWrapper or(Consumer<Criteria> consumer) {
		consumer.accept(or());

		return this;
	}

	public ExampleWrapper and(Consumer<Criteria> consumer) {
		consumer.accept(and());

		return this;
	}

	public List<ExampleHaving> getHavings() {
		return havings;
	}

	public List<ExampleJoin> getJoinTables() {
		return joinTables;
	}

	public void addJoinTable(ExampleJoin table) {
		this.joinTables.add(table);
	}

	public void addHaving(ExampleHaving having) {
		this.havings.add(having);
	}

	public void addHaving(ExampleHaving having, Object value) {
		if (value != null) this.havings.add(having);
	}

	public void addHavingOr(String condition, Object value) {
		if (condition != null && !condition.isEmpty() && value != null) {
			this.havings.add(ExampleHaving.or(String.format(condition, value)));
		}
	}

	public void addHavingAnd(String condition, Object value) {
		if (condition != null && !condition.isEmpty() && value != null) {
			this.havings.add(ExampleHaving.and(String.format(condition, value)));
		}
	}

	@Override
	public OrderBy orderBy(String property) {
		if (!(ORDERBY instanceof BOrderBy)) {
			this.ORDERBY = new BOrderBy(this, propertyMap);
		}

		return super.orderBy(property);
	}

	public Criteria createCriteria(Map<String, Object> param) {
		if (param == null || param.isEmpty()) { return null; }

		final Criteria criteria = super.or();

		for (final Entry<String, Object> entry : param.entrySet()) {
			criteria.andEqualTo(entry.getKey(), entry.getValue());
		}

		return criteria;
	}

	@Override
	protected Criteria createCriteriaInternal() {
		return new BCriteria(propertyMap, exists, notNull);
	}

	public String getGroupByClause() {
		return groupByClause;
	}

	public void setGroupByClause(String groupByClause) {
		this.groupByClause = groupByClause;
	}

	public static class BOrderBy extends OrderBy {
		private Boolean isProperty;
		private Example example;

		public BOrderBy(Example example, Map<String, EntityColumn> propertyMap) {
			super(example, propertyMap);

			this.example = example;
		}

		@Override
		public BOrderBy orderBy(String property) {
			String column = property(property);

			// 判断排序参数是否有效 , 如果有效就添加到排序规则
			if (StringUtil.isNotEmpty(column)) {
				this.append(column, true);
				this.isProperty = true;
			} else {
				this.isProperty = false;
			}

			return this;
		}

		@Override
		public BOrderBy desc() {
			if (isProperty != null && isProperty) {
				this.isProperty = false;
				this.append(" DESC", false);
			}

			return this;
		}

		@Override
		public BOrderBy asc() {
			if (isProperty != null && isProperty) {
				this.isProperty = false;
				this.append(" ASC", false);
			}

			return this;
		}

		private void append(String orderRule, boolean split) {
			StringBuilder builder = new StringBuilder();

			// 将已经存在的排序规则添加到前面
			if (StringUtil.isNotEmpty(example.getOrderByClause())) {
				builder.append(example.getOrderByClause());

				if (split && StringUtil.isNotEmpty(orderRule)) {
					builder.append(",").append(orderRule);
				} else {
					builder.append(orderRule);
				}
			} else {
				builder.append(orderRule);
			}

			// 更新example中的排序规则
			this.example.setOrderByClause(builder.toString());
		}

		private String property(String property) {
			if (StringUtil.isEmpty(property)) {
				throw new MapperException("接收的property为空！");
			} else if (propertyMap.containsKey(property)) {
				return propertyMap.get(property).getColumn();
			} else {
				return StrUtil.trim(property);
			}
		}
	}

	public static class BCriteria extends Criteria {

		protected BCriteria(Map<String, EntityColumn> propertyMap, boolean exists, boolean notNull) {
			super(propertyMap, exists, notNull);
		}

		@Override
		public Criteria orCondition(String condition, Object value) {
			if (value != null && !"".equals(value)) {
				return super.orCondition(condition, value);
			} else {
				return this;
			}
		}

		@Override
		public Criteria andCondition(String condition, Object value) {
			if (value != null && !"".equals(value)) {
				return super.andCondition(condition, value);
			} else {
				return this;
			}
		}
	}

}